// Package timer 定时器包
//1、提供定时器时间调度
//2、提供cronexpres格式的定时器操作
package timer

import (
	"context"
	"errors"
	"fmt"
	"runtime"
	"time"
	cst "wanyu/server/engine/globaldef/consts"
	"wanyu/server/engine/wplog"
)

//Cb 延时回调函数
type Cb func(id int, arg interface{})

//Param 回调函数参数类型
type Param interface{}

//Scheduler 时间调度接口，每个协程一个Scheduler
type Scheduler interface {
	Schedule(d time.Duration, repeat uint, cb Cb, param Param) (id int, err error)
	ScheduleCron(cronExpr *CronExpr, cb Cb, param Param) (id int, err error)
	Cancel(id int) (err error)
	ReceiveChan() chan interface{}
	Exec(in interface{}) (err error)
	Stop()
}

//scheduler 数据结构
type scheduler struct {
	idCounter int
	ctx       context.Context
	timers    map[int]*timer
	chanRet   chan interface{}
	chanStop  chan bool
}

//NewScheduler 申请一个时间调度器接口
func NewScheduler(l int) Scheduler {

	ctx, cancel := context.WithCancel(context.Background())
	stop := make(chan bool, 1)
	go func() {
		<-stop
		cancel()
	}()

	sd := &scheduler{
		idCounter: 1,
		ctx:       ctx,
		timers:    make(map[int]*timer),
		chanRet:   make(chan interface{}, l),
		chanStop:  stop,
	}
	wplog.Infof("NewScheduler,l:%v", l)
	return sd
}

//Timer 时间定时器
type timer struct {
	id     int
	t      *time.Ticker
	cb     Cb
	arg    Param
	cancel context.CancelFunc
}

//RetInfo 返回信息
type retInfo struct {
	id    int
	count uint
}

//Stop 停止
func (t *timer) stop() {
	t.cancel()
	t.t.Stop()
}

//Cb 处理回调
func (t *timer) Cb() (err error) {
	defer func() {
		if r := recover(); r != nil {
			t.cb = nil
			if cst.LenStackBuf > 0 {
				buf := make([]byte, cst.LenStackBuf)
				l := runtime.Stack(buf, false)
				err = fmt.Errorf("%v: %s", r, buf[:l])
			} else {
				err = fmt.Errorf("%v", r)
			}
		}
	}()

	if t.cb != nil {
		t.cb(t.id, t.arg)
	}
	return nil
}

//Schedule 时间调度
//d 延时时间
//repeat 重复次数
//cb 回调函数
//param 参数
func (sd *scheduler) Schedule(d time.Duration, repeat uint, cb Cb, param Param) (id int, err error) {
	sd.idCounter = sd.idCounter + 1
	id = sd.idCounter
	err = sd.schedule(id, d, repeat, cb, param)
	return
}

//ScheduleCron Cron调度
//cronExpr cron express表达式
//cb 回调函数
//param 参数
func (sd *scheduler) ScheduleCron(cronExpr *CronExpr, cb Cb, param Param) (id int, err error) {
	now := time.Now()
	nextTime := cronExpr.Next(now)
	if nextTime.IsZero() {
		return 0, errors.New("scheduler:ScheduleCron,cron error")
	}

	sd.idCounter = sd.idCounter + 1
	id = sd.idCounter

	// callback
	var cbT Cb
	cbT = func(timerID int, arg interface{}) {
		defer cb(id, arg)

		now := time.Now()
		nextTime := cronExpr.Next(now)
		if nextTime.IsZero() {
			return
		}
		sd.schedule(timerID, nextTime.Sub(now), 1, cbT, param)
	}
	err = sd.schedule(id, nextTime.Sub(now), 1, cbT, param)
	return
}

//schedule 调度底层接口
func (sd *scheduler) schedule(id int, d time.Duration, repeat uint,
	cb Cb, param Param) (err error) {

	if d <= 0 {
		return errors.New("scheduler:schedule,arg d must be greater than 0")
	}

	if repeat == 0 {
		return errors.New("scheduler:schedule,arg repeat must be greater than 0")
	}

	ctx, cancel := context.WithCancel(sd.ctx)
	t := &timer{
		id:     id,
		cb:     cb,
		arg:    param,
		t:      time.NewTicker(d),
		cancel: cancel,
	}
	go func(done <-chan struct{}, ct <-chan time.Time, id int, count uint) {
		for i := uint(0); i < count; i++ {
			select {
			case <-ct:
				ret := &retInfo{
					id:    id,
					count: count - i - 1,
				}
				sd.chanRet <- *ret
			case <-done:
				return
			}
		}
	}(ctx.Done(), t.t.C, t.id, repeat)

	sd.timers[t.id] = t
	return nil
}

//Schedule 取消一个定时器
func (sd *scheduler) Cancel(id int) error {
	if t, ok := sd.timers[id]; ok {
		t.stop()
		delete(sd.timers, id)
	} else {
		return fmt.Errorf("function Cancel %v: doesn't exit", id)
	}
	return nil
}

func (sd *scheduler) ReceiveChan() chan interface{} {
	return sd.chanRet
}

//Exec 执行超时的定时器
func (sd *scheduler) Exec(in interface{}) (err error) {
	ret, ok := in.(retInfo)
	if ok == false {
		return fmt.Errorf("scheduler:Exec,in type error")
	}
	t, ok := sd.timers[ret.id]
	if ok == false {
		return fmt.Errorf("scheduler:Exec,%v doesn't exit", ret.id)
	}
	//计数等于0，移除该定时器
	if ret.count == 0 {
		delete(sd.timers, ret.id)
	}
	return t.Cb()
}

//Stop 停止所有定时器
func (sd *scheduler) Stop() {
	sd.chanStop <- true
}
